#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import argparse
from configparser import ConfigParser
import datetime
import json
import logging
import logging.handlers
import logging.config
import os
import sys
from urllib import parse
import re
import socket
import signal
import threading

from daemon import Daemon

from http.server import ThreadingHTTPServer,BaseHTTPRequestHandler

import paramiko
#logging.basicConfig(level=logging.DEBUG)
log=logging.getLogger()
#  getIvrLog所在的目录
configDir=''

def cmd():
    args = argparse.ArgumentParser(description = 'getIvrLog用于获取IVR日志的http接口,可以使用 ./getIvrLog.py -h 查询帮助')
    #必写属性,第一位
    #args.add_argument("action",type = str,help = "动作类型:\r start: 启动urcpServer 并自动到后台. 可以加-c 指定urcpServer.ini文件路径 \rstop:默认停止本机的urcpServer. 可以加 -a 参数,停止 -a 指定服务器的urcpServer.\n reload:重新加载urcpServer.ini的配置文件.\ntest:测试本机的urcpServer服务是否有问题 可以加-a ${address} 测试远程的urcpServer是否有问题",choices=['start','stop','reload','help','test'],default='help')
    args.add_argument("action",type = str,help ="""动作类型:
                     start: 启动getIvrLog服务器 并自动到后台. 
                     stop:停止本机的getIvrLog服务器,
       """,choices=['start','stop'])
    args = args.parse_args()#返回一个命名空间,如果想要使用变量,可用args.attr
    log.info('args:'+str(args))
    return args
def start_server(config:ConfigParser):
    config=getConfig()
    httpPort=config.getint('http','port')
    http_server = ThreadingHTTPServer(("", int(httpPort)), LogHttpHandle)
    log.info('http server started,port:'+str(httpPort))
    try:
        http_server.serve_forever()  # 设置一直监听并接收请求
    except KeyboardInterrupt:
        pass
    http_server.server_close()
    log.info('HTTP server closed')



def getLogTime(callId:str):
    """
    获取呼叫时间。格式：mmdd24mi
    """
    # 2223022314582882801769 -> 02231450
    return callId[4:11]+'0'
def getConfig()->ConfigParser:
    """
    读取配置文件信息
    """
    config=ConfigParser()
    propertyFile=configDir+'getIvrLog.ini'
    log.debug('read properity file:'+propertyFile)
    config.read(propertyFile)
    return config
class StartHttpServer(Daemon):
    def __init__(self,httpPort:int):
        signal.signal(signal.SIGTERM,self.destroy)
        signal.signal(signal.SIGINT,self.destroy)
        pidFile=configDir+'getIvrLog.pid'
        self.httpPort=httpPort
        self.http_server=None
        log.debug('prepare starting ')
        log.debug('set pid to file:'+pidFile)
        super().__init__(pidFile)


    def _run(self):
        #httpPort=self.config.getint('http','port')
        self.http_server = ThreadingHTTPServer(("",self.httpPort), LogHttpHandle)
        log.info('http server started,port:'+str(self.httpPort))
        try:
            self.http_server.serve_forever()  # 设置一直监听并接收请求
        except KeyboardInterrupt:
            pass
        self.http_server.server_close()
        log.info('HTTP server closed')
    def destroy(self,signal_num,frame)->None:
        log.info('shutdown http server')
        if self.http_server is not None:
            #需要用另外一个线程来停止
            stopThread=threading.Thread(target=self.http_server.shutdown)
            stopThread.start()
            #self.http_server.shutdown()


class CallResult:
    def __init__(self,bResult:bool,messge:str='',result:list=None) -> None:
        self.success=bResult
        self.errMsg=messge
        self.result=result
    def __str__(self) -> str:
        strList=['success='+str(self.success)]
        if not self.success:
            strList.append('errMsg='+str(self.errMsg))
        strList.append('result='+str(self.result))
        return 'CallResult['+','.join(strList)+']'
    
class LogHttpHandle(BaseHTTPRequestHandler):
    """
     1.get http://ip:port/log/${callId}

    """
    def setup(self):
        self.request.settimeout(30)
        BaseHTTPRequestHandler.setup(self)
    def do_GET(self):
        """
        目前只支持/getFile/xxx
        curl http://127.0.0.1:8080/searchCTI/2223022314582882801769

        curl 'http://127.0.0.1:8080/searchIVR/2223022314582882801769?ivrIp=192.168.0.214&ivrPort=06086'
        url -o 1.log http://127.0.0.1:8080/log/2223022314582882801769
        """
        log.debug('run get .url ,path:'+str(self.path))
        if self.path.startswith('/log/'):
            return self._do_getFile()
        elif self.path.startswith('/searchCTI/'):
            return self._searchCTI()
        elif self.path.startswith('/searchIVR/'):
            return self._searchIVR()
        else:
            self._outputResponse(CallResult(False,'未识别的path:'+self.path))
    
    def _searchCTI(self):
        """
        查询并生成ivr日志
        """
        callId=self.path[len('/searchCTI/'):]
        if len(callId)!=22:
            log.warning('不是合法的callId:'+callId)
            return self._outputResponse(CallResult(False,'不是合法的callId'))

        config=getConfig()
        ctiLog=CtiLog(config)
        callResult=ctiLog.searchCTI(callId)
        return self._outputResponse(callResult)
    def _searchIVR(self):
        """
        curl 'http://127.0.0.1:8080/searchIVR/2223022314582882801769?ivrIp=192.168.0.214&ivrPort=06086'
        """
        url=parse.urlparse(self.path)
        callId=url.path[len('/searchIVR/'):]
        log.info('query:'+url.query+',callId:'+callId)
        paramDict=parse.parse_qs(url.query)
        ivrIp=paramDict.get('ivrIp',[None])[0]
        ivrPort=paramDict.get('ivrPort',[None])[0]
        if callId is None or ivrIp is None or ivrPort is None:
            return self._outputResponse(CallResult(False,'callId,ivrIp,ivrPort不允许为空'))
        if len(callId)!=22:
            return self._outputResponse(CallResult(False,'不是合法的callId'))
        config=getConfig()
        ivrLog=IvrLog(config)
        callResult=ivrLog.searchIvr(callId,ivrIp,int(ivrPort))
        return self._outputResponse(callResult)
        
    def _outputResponse(self, callResult:CallResult)->None:
        """
         生成返回值
        """
        self.protocal_version = 'HTTP/1.1'
        self.send_response(200)
        self.send_header('Content-type', 'application/json;charset=UTF-8')
        self.end_headers()
        log.info('返回结果:'+str(callResult))
        resultDict={}
        if callResult.success:
            resultDict['code']=200
            resultDict['result']=callResult.result
        else:
            resultDict['code']=500
            resultDict['message']=callResult.errMsg
        jsonStr=json.dumps(resultDict)
        buf = bytes(jsonStr, encoding="utf-8")
        self.wfile.write(buf)
    def _do_getFile(self)->None:
        """
        返回获取的文件
        """
        fileName=self.path[len('/log/'):]+'.log'
        filePath=configDir+'log/'+fileName
        if not os.path.exists(filePath):
           self.send_response(404)
           self.send_error(404,'not find file:'+fileName)
           log.warning('没有找到文件:'+filePath)
           return 
        self.send_response(200)
        self.send_header('Content-type', 'application/txt')
        self.send_header('Content-Disposition','attachment;filename="'+os.path.basename(fileName)+'"')
        self.end_headers()
        # read byte so rb+
        with open(filePath,'rb+') as f:
            while True:
                c=f.read(1024)
                if c:
                    self.wfile.write(c)
                else:
                    break
        return None
class CtiLog:
    def __init__(self,config:ConfigParser) -> None:
        self._config=config
    def _check(self,callId:str)->dict:
        """
        验证callId对应的配置是否存在
        @dict{ip,user,password,logFile}
        """
        nodeId='node_'+callId[:2]
        if not  self._config.has_section(nodeId):
            log.warning(f'not find node:{nodeId} in getIvrLog.ini. please check')
            return None
        sshDict={}
        for key,value in self._config[nodeId].items():
            sshDict[key]=value
        logTime=getLogTime(callId)
        logFile='tmserver'+logTime+'.log'
        sshDict['logFile']=logFile
        return sshDict
    
    def connectCTIssh(self,callId:str):
        """
          采用ssh方式连接到服务器。
        """
        sshDict=self._check(callId)
        if sshDict is None:
            return CallResult(False,f'配置文件中没有nodeId:{callId[:2]}的配置信息')

        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        callResult=CallResult(False)
        try:
            client.connect(sshDict['ip'], 22, username=sshDict['user'], password=sshDict['password'], timeout=4)
            #commands=['cd /home/'+sshDict['user']+'/LOG/tmserver','grep '+sshDict['logFile']]
            commands=['grep '+callId+' LOG/tmserver/'+sshDict['logFile']]
            log.debug('连接CTI成功,'+str(sshDict)+'命令:'+str(commands))
            stdin, stdout, stderr = client.exec_command(';'.join(commands),timeout=4)
            errMsgList=stderr.readlines()
            if len(errMsgList)>0:
                callResult.success=False
                callResult.errMsg=f'执行CTI的命令异常:{str(commands)}'
                log.warning(f'callId:{callId},执行CTI的命令异常:{str(commands)},错误信息:'+str(errMsgList))
                return callResult
            
            stdoutStrList=[]
            for st in stdout.readlines():
                stdoutStrList.append(st)
            callResult.success=True
            callResult.result=stdoutStrList
            log.debug('从CTI获取结果:'+''.join(stdoutStrList))
        except paramiko.AuthenticationException:
            log.exception('连接CTI异常，可能是密码错误,'+str(sshDict))
            callResult.success=False
            callResult.errMsg='连接CTI异常，可能是密码错误'
        except paramiko.SSHException:
            log.exception('连接CTI异常，执行命令错误,'+str(sshDict))
            callResult.success=False
            callResult.errMsg='连接CTI异常，执行命令错误'
        except socket.timeout:
            log.exception('读取CTI数据超时'+str(sshDict))
            callResult.success=False
            callResult.errMsg='读取CTI数据超时'
        finally:
            client.close()
        return callResult
    def searchCTI(self,callId:str):
        """
         获取CTI上的IVR信息。
         @Return [result:[{ivrIp:,ivrPort:,callTime:,callId:,logTime:,callSeq}]]
        """
        callResult=self.connectCTIssh(callId)
        if not callResult.success:
            return callResult
        ctiResultList=callResult.result
        callObjectList=[]
        for ctiString in ctiResultList:
            if ctiString.find('UNCC_IAI')==-1 and ctiString.find('UNCC_HLD')==-1:
                continue
            callObject={}
            callObject['ivrIp']=re.findall('(\d+\.\d+\.\d+\.\d+)',ctiString)[0]
            callObject['ivrPort']=re.findall('(/\d+)',ctiString)[0].replace('/','')
            callObject['callTime']=re.findall('(\d\d:\d\d)',ctiString)[0]
            callObject['callId']=callId
            callObject['logTime']=getLogTime(callId)
            callObject['callSeq']=re.findall('(SessionNo=\d+)',ctiString)[0].replace('SessionNo=','')
            try:
                callObject['caller']=re.findall('(ANI=\d+)',ctiString)[0].replace('ANI=','')
            except IndexError:
                callObject['caller']=''
            try:
                callObject['callee']=re.findall('(DNIS2=\d+)',ctiString)[0].replace('DNIS2=','')
            except IndexError:
                callObject['callee']=''
            callObjectList.append(callObject)
        if len(callObjectList)>0:
            return CallResult(True,'',callObjectList)
        else:
            return CallResult(False,'没有在CTI日志中找到callId:'+callId+'的记录')


class IvrLog:
    def __init__(self,config:ConfigParser) -> None:
        self._config=config
        self.localPath=configDir+'log/'
    def _check(self,nodeId:str,ivrIp:str)->dict:
        nodeId='node_'+nodeId
        ivrsKey=self._config.get(nodeId,'ivrs')
        if ivrsKey is None:
            log.warning(f'not find node:{nodeId},ivrs key in getIvrLog.ini. please check')
            return None
        ivrKeyList=ivrsKey.split(',')
        ivrDict={}
        for ivrKey in ivrKeyList:
            if self._config.get(ivrKey,'ip')==ivrIp:
                for key,value in self._config[ivrKey].items():
                    ivrDict[key]=value
                log.debug('get ivr config:'+str(ivrDict))
                break
        if len(ivrDict)==0:
            log.warning(f'not find ivrIp:{ivrIp} , nodeId:{nodeId} in getIvrLog.ini,please check!')
            return None
        
        return ivrDict


    def connectIvr(self,callId:str,ivrIp:str,ivrPort:int,logTime:str=None):
       sshDict=self._check(callId[:2],ivrIp)
       if sshDict is None:
        return CallResult(False,f'配置文件中没有ivrIp:{ivrIp}的配置')
       client = paramiko.SSHClient()
       client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
       callResult=CallResult(False)
       ftp=None
       try:
           if logTime is None:
              logTime=getLogTime(callId)
           logFile='svcsmgr'+logTime+'.log'
           callIdLog=callId+'.log'
           client.connect(sshDict['ip'], 22, username=sshDict['user'], password=sshDict['password'], timeout=10)
           #commands=['cd /home/'+sshDict['user']+'/LOG/tmserver','grep '+sshDict['logFile']]
           commands=['grep '+'\'>'+str(ivrPort)+'<\''+' LOG/c3svcsmgr/'+logFile+' >'+callIdLog]
           log.debug('连接IVR成功,'+str(sshDict)+'命令:'+str(commands))
           stdin, stdout, stderr = client.exec_command(';'.join(commands),timeout=4)
           errMsgList=stderr.readlines()
           if len(errMsgList)>0:
               callResult.success=False
               callResult.errMsg=f'执行IVR的命令异常:{str(commands)}'
               log.warning(f'callId:{callId},执行CTI的命令异常:{str(commands)},错误信息:'+str(errMsgList))
               return callResult
           for st in stdout.readlines():
               log.info('execute result:'+st)
           
           ftp = client.open_sftp()
           #remotePath,localPath
           ftp.get(callIdLog,self.localPath+callIdLog)
           ftp.remove(callIdLog)
           log.debug('从IVR获取文件结果:'+callId+'.log')
           callResult.result=self.localPath+callIdLog
           callResult.success=True
       except paramiko.AuthenticationException:
           log.exception('连接IVR异常，可能是密码错误,'+str(sshDict))
           callResult.success=False
           callResult.errMsg='连接IVR异常，可能是密码错误'
       except paramiko.SSHException:
           log.exception('连接IVR异常，执行命令错误,'+str(sshDict))
           callResult.success=False
           callResult.errMsg='连接IVR异常，执行命令错误'
       except socket.timeout:
           log.exception('读取CTI数据超时'+str(sshDict))
           callResult.success=False
           callResult.errMsg='读取CTI数据超时'
       finally:
           if ftp is not None:
            ftp.close()
           client.close()
       return callResult
    def searchIvr(self,callId:str,ivrIp:str,ivrPort:int):
        """
        获取IVR日志，生成callId.log
        """
        callResult=self.connectIvr(callId,ivrIp,ivrPort)
        if not callResult.success:
            return callResult
        
        bStartReadFlag=False
        bIsFlowDataEndFlag=False
        flowdataLineList=[]
        for lineStrByte in open(callResult.result,'rb').readlines():
            lineStr=lineStrByte.decode('GBK')
            if lineStr.find('RECV UNCC_IAI')!=-1 or lineStr.find(callId)!=-1:
                bStartReadFlag=True
            if bStartReadFlag:
                flowdataLineList.append(lineStr)
            if bStartReadFlag and lineStr.find('FLOW-END--')>-1:
                bIsFlowDataEndFlag=True
                break
        if bStartReadFlag and not bIsFlowDataEndFlag:#流程日志还未结束
            nextLogTime=self.getNextLogTime(getLogTime(callId))
            callResult=self.connectIvr(callId,ivrIp,ivrPort,nextLogTime)
            if not callResult.success:
                return callResult
            for lineStrByte in open(callResult.result,'rb').readlines():
                lineStr=lineStrByte.decode('GBK')
                flowdataLineList.append(lineStr)
                if lineStr.find('FLOW-END--')!=-1:
                            break
        if len(flowdataLineList)>0:
            flowDataFileObject=open(self.localPath+callId+'.log','w')
            flowDataFileObject.writelines(flowdataLineList)
            flowDataFileObject.close()
            return callResult
        else:
            log.warning(f'{callId}:获取IVR的内容是空的')
            return CallResult(False,'获取的ivr日志是空')
    
    def getNextLogTime(self,currLogTime:str):
        """
          获取下个时间片。currLogTime,表示当前时间片，例如:03221330
        """
        currTime=datetime.datetime.today()
        logTimeStr=currTime.strftime('%Y')+currLogTime #yyyymmddhh24mi
        min10=datetime.timedelta(minutes=10)
        logTime=currTime.strptime(logTimeStr,'%Y%m%d%H%M')+min10 #加10分钟
        nextLogTimeStr=logTime.strftime('%m%d%H%M')[:-1]+'0'
        return nextLogTimeStr

if __name__ == '__main__':
    tempPath=os.path.split(sys.argv[0])#取文件名的路径。
    if tempPath[0]=='':#文件名采用绝对路径，而是采用相对路径时，取工作目录下的路径
        configDir=os.getcwd()+'/'
    else:
        configDir=tempPath[0]+'/'
    args=cmd()
    config=getConfig()
    httpPort=config.getint('http','port')
    #logging.basicConfig(level=logging.DEBUG)
    logging.config.fileConfig(config)
    if args.action=='start': #启动getIvrLog
        StartHttpServer(httpPort).start()
    elif args.action=='stop':
        StartHttpServer(httpPort).stop()
    """
    config=getConfig()
    start_server(config)

    """